/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sun.org.apache.xerces.internal.xpointer;

import java.util.Hashtable;
import java.util.Vector;

import com.sun.org.apache.xerces.internal.impl.Constants;
import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
import com.sun.org.apache.xerces.internal.util.SymbolTable;
import com.sun.org.apache.xerces.internal.util.XMLChar;
import com.sun.org.apache.xerces.internal.util.XMLSymbols;
import com.sun.org.apache.xerces.internal.xinclude.XIncludeHandler;
import com.sun.org.apache.xerces.internal.xinclude.XIncludeNamespaceSupport;
import com.sun.org.apache.xerces.internal.xni.Augmentations;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLErrorHandler;

/**
 * <p> This is a pipeline component which extends the XIncludeHandler to perform XPointer specific
 * processing specified in the W3C XPointerFramework and element() Scheme Recommendations. </p>
 *
 * <p> This component analyzes each event in the pipeline, looking for an element that matches a
 * PointerPart in the parent XInclude element's xpointer attribute value.  If the match succeeds,
 * all children are passed by this component. </p>
 *
 * <p> See the <a href="http://www.w3.org/TR/xptr-framework//">XPointer Framework Recommendation</a>
 * for more information on the XPointer Framework and ShortHand Pointers. See the <a
 * href="http://www.w3.org/TR/xptr-element/">XPointer element() Scheme Recommendation</a> for more
 * information on the XPointer element() Scheme. </p>
 *
 * @xerces.internal
 */
public final class XPointerHandler extends XIncludeHandler implements
    XPointerProcessor {

  // Fields
  // A Vector of XPointerParts
  protected Vector fXPointerParts = null;

  // The current XPointerPart
  protected XPointerPart fXPointerPart = null;

  // Has the fXPointerPart resolved successfully
  protected boolean fFoundMatchingPtrPart = false;

  // The XPointer Error reporter
  protected XMLErrorReporter fXPointerErrorReporter;

  // The XPointer Error Handler
  protected XMLErrorHandler fErrorHandler;

  // XPointerFramework symbol table
  protected SymbolTable fSymbolTable = null;

  // Supported schemes
  private final String ELEMENT_SCHEME_NAME = "element";

  // Has the XPointer resolved the subresource
  protected boolean fIsXPointerResolved = false;

  // Fixup xml:base and xml:lang attributes
  protected boolean fFixupBase = false;
  protected boolean fFixupLang = false;

  // ************************************************************************
  // Constructors
  // ************************************************************************

  /**
   *
   */
  public XPointerHandler() {
    super();

    fXPointerParts = new Vector();
    fSymbolTable = new SymbolTable();
  }

  public XPointerHandler(SymbolTable symbolTable,
      XMLErrorHandler errorHandler, XMLErrorReporter errorReporter) {
    super();

    fXPointerParts = new Vector();
    fSymbolTable = symbolTable;
    fErrorHandler = errorHandler;
    fXPointerErrorReporter = errorReporter;
    //fErrorReporter = errorReporter; // The XInclude ErrorReporter
  }

  // ************************************************************************
  //  Implementation of the XPointerProcessor interface.
  // ************************************************************************

  /**
   * Parses the XPointer framework expression and delegates scheme specific parsing.
   *
   * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#parseXPointer(java.lang.String)
   */
  public void parseXPointer(String xpointer) throws XNIException {

    // Initialize
    init();

    // tokens
    final Tokens tokens = new Tokens(fSymbolTable);

    // scanner
    Scanner scanner = new Scanner(fSymbolTable) {
      protected void addToken(Tokens tokens, int token)
          throws XNIException {
        if (token == Tokens.XPTRTOKEN_OPEN_PAREN
            || token == Tokens.XPTRTOKEN_CLOSE_PAREN
            || token == Tokens.XPTRTOKEN_SCHEMENAME
            || token == Tokens.XPTRTOKEN_SCHEMEDATA
            || token == Tokens.XPTRTOKEN_SHORTHAND) {
          super.addToken(tokens, token);
          return;
        }
        reportError("InvalidXPointerToken", new Object[]{tokens
            .getTokenString(token)});
      }
    };

    // scan the XPointer expression
    int length = xpointer.length();
    boolean success = scanner.scanExpr(fSymbolTable, tokens, xpointer, 0,
        length);

    if (!success) {
      reportError("InvalidXPointerExpression", new Object[]{xpointer});
    }

    while (tokens.hasMore()) {
      int token = tokens.nextToken();

      switch (token) {
        case Tokens.XPTRTOKEN_SHORTHAND: {

          // The shortHand name
          token = tokens.nextToken();
          String shortHandPointerName = tokens.getTokenString(token);

          if (shortHandPointerName == null) {
            reportError("InvalidXPointerExpression",
                new Object[]{xpointer});
          }

          XPointerPart shortHandPointer = new ShortHandPointer(
              fSymbolTable);
          shortHandPointer.setSchemeName(shortHandPointerName);
          fXPointerParts.add(shortHandPointer);
          break;
        }
        case Tokens.XPTRTOKEN_SCHEMENAME: {

          // Retreive the local name and prefix to form the scheme name
          token = tokens.nextToken();
          String prefix = tokens.getTokenString(token);
          token = tokens.nextToken();
          String localName = tokens.getTokenString(token);

          String schemeName = prefix + localName;

          // The next character should be an open parenthesis
          int openParenCount = 0;
          int closeParenCount = 0;

          token = tokens.nextToken();
          String openParen = tokens.getTokenString(token);
          if (openParen != "XPTRTOKEN_OPEN_PAREN") {

            // can not have more than one ShortHand Pointer
            if (token == Tokens.XPTRTOKEN_SHORTHAND) {
              reportError("MultipleShortHandPointers",
                  new Object[]{xpointer});
            } else {
              reportError("InvalidXPointerExpression",
                  new Object[]{xpointer});
            }
          }
          openParenCount++;

          // followed by zero or more ( and  the schemeData
          String schemeData = null;
          while (tokens.hasMore()) {
            token = tokens.nextToken();
            schemeData = tokens.getTokenString(token);
            if (schemeData != "XPTRTOKEN_OPEN_PAREN") {
              break;
            }
            openParenCount++;
          }
          token = tokens.nextToken();
          schemeData = tokens.getTokenString(token);

          // followed by the same number of )
          token = tokens.nextToken();
          String closeParen = tokens.getTokenString(token);
          if (closeParen != "XPTRTOKEN_CLOSE_PAREN") {
            reportError("SchemeDataNotFollowedByCloseParenthesis",
                new Object[]{xpointer});
          }
          closeParenCount++;

          while (tokens.hasMore()) {
            if (tokens.getTokenString(tokens.peekToken()) != "XPTRTOKEN_OPEN_PAREN") {
              break;
            }
            closeParenCount++;
          }

          // check if the number of open parenthesis are equal to the number of close parenthesis
          if (openParenCount != closeParenCount) {
            reportError("UnbalancedParenthesisInXPointerExpression",
                new Object[]{xpointer,
                    new Integer(openParenCount),
                    new Integer(closeParenCount)});
          }

          // Perform scheme specific parsing of the pointer part
          if (schemeName.equals(ELEMENT_SCHEME_NAME)) {
            XPointerPart elementSchemePointer = new ElementSchemePointer(
                fSymbolTable, fErrorReporter);
            elementSchemePointer.setSchemeName(schemeName);
            elementSchemePointer.setSchemeData(schemeData);

            // If an exception occurs while parsing the element() scheme expression
            // ignore it and move on to the next pointer part
            try {
              elementSchemePointer.parseXPointer(schemeData);
              fXPointerParts.add(elementSchemePointer);
            } catch (XNIException e) {
              // Re-throw the XPointer element() scheme syntax error.
              throw new XNIException(e);
            }

          } else {
            // ????
            reportWarning("SchemeUnsupported",
                new Object[]{schemeName});
          }

          break;
        }
        default:
          reportError("InvalidXPointerExpression",
              new Object[]{xpointer});
      }
    }

  }

  /**
   * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#resolveXPointer(com.sun.org.apache.xerces.internal.xni.QName,
   * com.sun.org.apache.xerces.internal.xni.XMLAttributes, com.sun.org.apache.xerces.internal.xni.Augmentations,
   * int event)
   */
  public boolean resolveXPointer(QName element, XMLAttributes attributes,
      Augmentations augs, int event) throws XNIException {
    boolean resolved = false;

    // The result of the first pointer part whose evaluation identifies
    // one or more subresources is reported by the XPointer processor as the
    // result of the pointer as a whole, and evaluation stops.
    // In our implementation, typically the first xpointer scheme that
    // matches an element is the document is considered.
    // If the pointer part resolved then use it, else search for the fragment
    // using next pointer part from lef-right.
    if (!fFoundMatchingPtrPart) {

      // for each element, attempt to resolve it against each pointer part
      // in the XPointer expression until a matching element is found.
      for (int i = 0; i < fXPointerParts.size(); i++) {

        fXPointerPart = (XPointerPart) fXPointerParts.get(i);

        if (fXPointerPart.resolveXPointer(element, attributes, augs,
            event)) {
          fFoundMatchingPtrPart = true;
          resolved = true;
        }
      }
    } else {
      if (fXPointerPart.resolveXPointer(element, attributes, augs, event)) {
        resolved = true;
      }
    }

    if (!fIsXPointerResolved) {
      fIsXPointerResolved = resolved;
    }

    return resolved;
  }

  /**
   * Returns true if the Node fragment is resolved.
   *
   * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#isFragmentResolved()
   */
  public boolean isFragmentResolved() throws XNIException {
    boolean resolved = (fXPointerPart != null) ? fXPointerPart.isFragmentResolved()
        : false;

    if (!fIsXPointerResolved) {
      fIsXPointerResolved = resolved;
    }

    return resolved;
  }

  /**
   * Returns true if the XPointer expression resolves to a non-element child
   * of the current resource fragment.
   *
   * @see com.sun.org.apache.xerces.internal.xpointer.XPointerPart#isChildFragmentResolved()
   */
  public boolean isChildFragmentResolved() throws XNIException {
    boolean resolved = (fXPointerPart != null) ? fXPointerPart
        .isChildFragmentResolved() : false;
    return resolved;
  }

  /**
   * Returns true if the XPointer successfully found a sub-resource .
   *
   * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#isFragmentResolved()
   */
  public boolean isXPointerResolved() throws XNIException {
    return fIsXPointerResolved;
  }

  /**
   * Returns the pointer part used to resolve the document fragment.
   *
   * @return String - The pointer part used to resolve the document fragment.
   */
  public XPointerPart getXPointerPart() {
    return fXPointerPart;
  }

  /**
   * Reports XPointer Errors
   */
  private void reportError(String key, Object[] arguments)
      throws XNIException {
        /*
        fXPointerErrorReporter.reportError(
                XPointerMessageFormatter.XPOINTER_DOMAIN, key, arguments,
                XMLErrorReporter.SEVERITY_ERROR);
        */
    throw new XNIException((fErrorReporter
        .getMessageFormatter(XPointerMessageFormatter.XPOINTER_DOMAIN))
        .formatMessage(fErrorReporter.getLocale(), key, arguments));
  }

  /**
   * Reports XPointer Warnings
   */
  private void reportWarning(String key, Object[] arguments)
      throws XNIException {
    fXPointerErrorReporter.reportError(
        XPointerMessageFormatter.XPOINTER_DOMAIN, key, arguments,
        XMLErrorReporter.SEVERITY_WARNING);
  }

  /**
   * Initializes error handling objects
   */
  protected void initErrorReporter() {
    if (fXPointerErrorReporter == null) {
      fXPointerErrorReporter = new XMLErrorReporter();
    }
    if (fErrorHandler == null) {
      fErrorHandler = new XPointerErrorHandler();
    }
        /*
         fXPointerErrorReporter.setProperty(Constants.XERCES_PROPERTY_PREFIX
         + Constants.ERROR_HANDLER_PROPERTY, fErrorHandler);
         */
    fXPointerErrorReporter.putMessageFormatter(
        XPointerMessageFormatter.XPOINTER_DOMAIN,
        new XPointerMessageFormatter());
  }

  /**
   * Initializes the XPointer Processor;
   */
  protected void init() {
    fXPointerParts.clear();
    fXPointerPart = null;
    fFoundMatchingPtrPart = false;
    fIsXPointerResolved = false;
    //fFixupBase = false;
    //fFixupLang = false;

    initErrorReporter();
  }

  /**
   * Returns a Vector of XPointerPart objects
   *
   * @return A Vector of XPointerPart objects.
   */
  public Vector getPointerParts() {
    return fXPointerParts;
  }

  /**
   * List of XPointer Framework tokens.
   *
   * @xerces.internal
   */
  private final class Tokens {

    /**
     * XPointer Framework tokens
     * [1] Pointer     ::= Shorthand | SchemeBased
     * [2] Shorthand   ::= NCName
     * [3] SchemeBased ::= PointerPart (S? PointerPart)*
     * [4] PointerPart ::= SchemeName '(' SchemeData ')'
     * [5] SchemeName  ::= QName
     * [6] SchemeData  ::= EscapedData*
     * [7] EscapedData ::= NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
     * [8] NormalChar  ::= UnicodeChar - [()^]
     * [9] UnicodeChar ::= [#x0-#x10FFFF]
     */
    private static final int XPTRTOKEN_OPEN_PAREN = 0,
        XPTRTOKEN_CLOSE_PAREN = 1, XPTRTOKEN_SHORTHAND = 2,
        XPTRTOKEN_SCHEMENAME = 3, XPTRTOKEN_SCHEMEDATA = 4;

    // Token names
    private final String[] fgTokenNames = {"XPTRTOKEN_OPEN_PAREN",
        "XPTRTOKEN_CLOSE_PAREN", "XPTRTOKEN_SHORTHAND",
        "XPTRTOKEN_SCHEMENAME", "XPTRTOKEN_SCHEMEDATA"};

    // Token count
    private static final int INITIAL_TOKEN_COUNT = 1 << 8;

    private int[] fTokens = new int[INITIAL_TOKEN_COUNT];

    private int fTokenCount = 0;

    // Current token position
    private int fCurrentTokenIndex;

    private SymbolTable fSymbolTable;

    private Hashtable fTokenNames = new Hashtable();

    /**
     * Constructor
     *
     * @param symbolTable SymbolTable
     */
    private Tokens(SymbolTable symbolTable) {
      fSymbolTable = symbolTable;

      fTokenNames.put(new Integer(XPTRTOKEN_OPEN_PAREN),
          "XPTRTOKEN_OPEN_PAREN");
      fTokenNames.put(new Integer(XPTRTOKEN_CLOSE_PAREN),
          "XPTRTOKEN_CLOSE_PAREN");
      fTokenNames.put(new Integer(XPTRTOKEN_SHORTHAND),
          "XPTRTOKEN_SHORTHAND");
      fTokenNames.put(new Integer(XPTRTOKEN_SCHEMENAME),
          "XPTRTOKEN_SCHEMENAME");
      fTokenNames.put(new Integer(XPTRTOKEN_SCHEMEDATA),
          "XPTRTOKEN_SCHEMEDATA");
    }

    /**
     * Returns the token String
     *
     * @param token The index of the token
     * @return String The token string
     */
    private String getTokenString(int token) {
      return (String) fTokenNames.get(new Integer(token));
    }

    /**
     * Add the specified string as a token
     *
     * @param token The token string
     */
    private void addToken(String tokenStr) {
      Integer tokenInt = (Integer) fTokenNames.get(tokenStr);
      if (tokenInt == null) {
        tokenInt = new Integer(fTokenNames.size());
        fTokenNames.put(tokenInt, tokenStr);
      }
      addToken(tokenInt.intValue());
    }

    /**
     * Add the specified int token
     *
     * @param token The int specifying the token
     */
    private void addToken(int token) {
      try {
        fTokens[fTokenCount] = token;
      } catch (ArrayIndexOutOfBoundsException ex) {
        int[] oldList = fTokens;
        fTokens = new int[fTokenCount << 1];
        System.arraycopy(oldList, 0, fTokens, 0, fTokenCount);
        fTokens[fTokenCount] = token;
      }
      fTokenCount++;
    }

    /**
     * Resets the current position to the head of the token list.
     */
    private void rewind() {
      fCurrentTokenIndex = 0;
    }

    /**
     * Returns true if the {@link #getNextToken()} method
     * returns a valid token.
     */
    private boolean hasMore() {
      return fCurrentTokenIndex < fTokenCount;
    }

    /**
     * Obtains the token at the current position, then advance
     * the current position by one.
     *
     * throws If there's no such next token, this method throws
     * <tt>new XNIException("XPointerProcessingError");</tt>.
     */
    private int nextToken() throws XNIException {
      if (fCurrentTokenIndex == fTokenCount) {
        reportError("XPointerProcessingError", null);
      }
      return fTokens[fCurrentTokenIndex++];
    }

    /**
     * Obtains the token at the current position, without advancing
     * the current position.
     *
     * If there's no such next token, this method throws
     * <tt>new XNIException("XPointerProcessingError");</tt>.
     */
    private int peekToken() throws XNIException {
      if (fCurrentTokenIndex == fTokenCount) {
        reportError("XPointerProcessingError", null);
      }
      return fTokens[fCurrentTokenIndex];
    }

    /**
     * Obtains the token at the current position as a String.
     *
     * If there's no current token or if the current token
     * is not a string token, this method throws
     * If there's no such next token, this method throws
     * <tt>new XNIException("XPointerProcessingError");</tt>.
     */
    private String nextTokenAsString() throws XNIException {
      String tokenStrint = getTokenString(nextToken());
      if (tokenStrint == null) {
        reportError("XPointerProcessingError", null);
      }
      return tokenStrint;
    }
  }

  /**
   * The XPointer expression scanner.  Scans the XPointer framework expression.
   *
   * @xerces.internal
   */
  private class Scanner {

    /**
     * 7-bit ASCII subset
     *
     * 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
     * 0,  0,  0,  0,  0,  0,  0,  0,  0, HT, LF,  0,  0, CR,  0,  0,  // 0
     * 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1
     * SP,  !,  ",  #,  $,  %,  &,  ',  (,  ),  *,  +,  ,,  -,  .,  /,  // 2
     * 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  :,  ;,  <,  =,  >,  ?,  // 3
     *
     * @, A,  B,  C,  D,  E,  F,  G,  H,  I,  J,  K,  L,  M,  N,  O,  // 4 P,  Q,  R,  S,  T,  U,
     * V,  W,  X,  Y,  Z,  [,  \,  ],  ^,  _,  // 5 `,  a,  b,  c,  d,  e,  f,  g,  h,  i,  j,  k,
     * l,  m,  n,  o,  // 6 p,  q,  r,  s,  t,  u,  v,  w,  x,  y,  z,  {,  |,  },  ~, DEL  // 7
     */
    private static final byte CHARTYPE_INVALID = 0, // invalid XML character
        CHARTYPE_OTHER = 1, // not special - one of "#%&;?\`{}~" or DEL
        CHARTYPE_WHITESPACE = 2, // one of "\t\n\r " (0x09, 0x0A, 0x0D, 0x20)
        CHARTYPE_CARRET = 3, // ^
        CHARTYPE_OPEN_PAREN = 4, // '(' (0x28)
        CHARTYPE_CLOSE_PAREN = 5, // ')' (0x29)
        CHARTYPE_MINUS = 6, // '-' (0x2D)
        CHARTYPE_PERIOD = 7, // '.' (0x2E)
        CHARTYPE_SLASH = 8, // '/' (0x2F)
        CHARTYPE_DIGIT = 9, // '0'-'9' (0x30 to 0x39)
        CHARTYPE_COLON = 10, // ':' (0x3A)
        CHARTYPE_EQUAL = 11, // '=' (0x3D)
        CHARTYPE_LETTER = 12, // 'A'-'Z' or 'a'-'z' (0x41 to 0x5A and 0x61 to 0x7A)
        CHARTYPE_UNDERSCORE = 13, // '_' (0x5F)
        CHARTYPE_NONASCII = 14; // Non-ASCII Unicode codepoint (>= 0x80)

    private final byte[] fASCIICharMap = {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
        0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        2, 1, 1, 1, 1, 1, 1, 1, 4, 5, 1, 1, 1, 6, 7, 8, 9, 9, 9, 9, 9,
        9, 9, 9, 9, 9, 10, 1, 1, 11, 1, 1, 1, 12, 12, 12, 12, 12, 12,
        12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
        12, 12, 12, 12, 1, 1, 1, 3, 13, 1, 12, 12, 12, 12, 12, 12, 12,
        12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
        12, 12, 12, 1, 1, 1, 1, 1};

    //
    // Data
    //
    /**
     * Symbol table.
     */
    private SymbolTable fSymbolTable;

    /**
     * Constructs an XPointer Framework expression scanner.
     *
     * @param symbolTable SymbolTable
     */
    private Scanner(SymbolTable symbolTable) {
      // save pool and tokens
      fSymbolTable = symbolTable;

    } // <init>(SymbolTable)

    /**
     * Scans the XPointer Expression
     */
    private boolean scanExpr(SymbolTable symbolTable, Tokens tokens,
        String data, int currentOffset, int endOffset)
        throws XNIException {

      int ch;
      int openParen = 0;
      int closeParen = 0;
      int nameOffset, dataOffset;
      boolean isQName = false;
      String name = null;
      String prefix = null;
      String schemeData = null;
      StringBuffer schemeDataBuff = new StringBuffer();

      while (true) {

        if (currentOffset == endOffset) {
          break;
        }
        ch = data.charAt(currentOffset);

        //
        while (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D) {
          if (++currentOffset == endOffset) {
            break;
          }
          ch = data.charAt(currentOffset);
        }
        if (currentOffset == endOffset) {
          break;
        }

        //
        // [1]    Pointer      ::=    Shorthand | SchemeBased
        // [2]    Shorthand    ::=    NCName
        // [3]    SchemeBased  ::=    PointerPart (S? PointerPart)*
        // [4]    PointerPart  ::=    SchemeName '(' SchemeData ')'
        // [5]    SchemeName   ::=    QName
        // [6]    SchemeData   ::=    EscapedData*
        // [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
        // [8]    NormalChar   ::=    UnicodeChar - [()^]
        // [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
        // [?]    QName        ::=    (NCName ':')? NCName
        // [?]    NCName       ::=    (Letter | '_') (NCNameChar)*
        // [?]    NCNameChar   ::=    Letter | Digit | '.' | '-' | '_'  (ascii subset of 'NCNameChar')
        // [?]    Letter       ::=    [A-Za-z]                              (ascii subset of 'Letter')
        // [?]    Digit        ::=    [0-9]                                  (ascii subset of 'Digit')
        //
        byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
            : fASCIICharMap[ch];

        switch (chartype) {

          case CHARTYPE_OPEN_PAREN: // '('
            addToken(tokens, Tokens.XPTRTOKEN_OPEN_PAREN);
            openParen++;
            ++currentOffset;
            break;

          case CHARTYPE_CLOSE_PAREN: // ')'
            addToken(tokens, Tokens.XPTRTOKEN_CLOSE_PAREN);
            closeParen++;
            ++currentOffset;
            break;

          case CHARTYPE_CARRET:
          case CHARTYPE_COLON:
          case CHARTYPE_DIGIT:
          case CHARTYPE_EQUAL:
          case CHARTYPE_LETTER:
          case CHARTYPE_MINUS:
          case CHARTYPE_NONASCII:
          case CHARTYPE_OTHER:
          case CHARTYPE_PERIOD:
          case CHARTYPE_SLASH:
          case CHARTYPE_UNDERSCORE:
          case CHARTYPE_WHITESPACE:
            // Scanning SchemeName | Shorthand
            if (openParen == 0) {
              nameOffset = currentOffset;
              currentOffset = scanNCName(data, endOffset,
                  currentOffset);

              if (currentOffset == nameOffset) {
                reportError("InvalidShortHandPointer",
                    new Object[]{data});
                return false;
              }

              if (currentOffset < endOffset) {
                ch = data.charAt(currentOffset);
              } else {
                ch = -1;
              }

              name = symbolTable.addSymbol(data.substring(nameOffset,
                  currentOffset));
              prefix = XMLSymbols.EMPTY_STRING;

              // The name is a QName => a SchemeName
              if (ch == ':') {
                if (++currentOffset == endOffset) {
                  return false;
                }

                ch = data.charAt(currentOffset);
                prefix = name;
                nameOffset = currentOffset;
                currentOffset = scanNCName(data, endOffset,
                    currentOffset);

                if (currentOffset == nameOffset) {
                  return false;
                }

                if (currentOffset < endOffset) {
                  ch = data.charAt(currentOffset);
                } else {
                  ch = -1;
                }

                isQName = true;
                name = symbolTable.addSymbol(data.substring(
                    nameOffset, currentOffset));
              }

              // REVISIT:
              if (currentOffset != endOffset) {
                addToken(tokens, Tokens.XPTRTOKEN_SCHEMENAME);
                tokens.addToken(prefix);
                tokens.addToken(name);
                isQName = false;
              } else if (currentOffset == endOffset) {
                // NCName => Shorthand
                addToken(tokens, Tokens.XPTRTOKEN_SHORTHAND);
                tokens.addToken(name);
                isQName = false;
              }

              // reset open/close paren for the next pointer part
              closeParen = 0;

              break;

            } else if (openParen > 0 && closeParen == 0 && name != null) {
              // Scanning SchemeData
              dataOffset = currentOffset;
              currentOffset = scanData(data, schemeDataBuff,
                  endOffset, currentOffset);

              if (currentOffset == dataOffset) {
                reportError("InvalidSchemeDataInXPointer",
                    new Object[]{data});
                return false;
              }

              if (currentOffset < endOffset) {
                ch = data.charAt(currentOffset);
              } else {
                ch = -1;
              }

              schemeData = symbolTable.addSymbol(schemeDataBuff
                  .toString());
              addToken(tokens, Tokens.XPTRTOKEN_SCHEMEDATA);
              tokens.addToken(schemeData);

              // reset open/close paren for the next pointer part
              openParen = 0;
              schemeDataBuff.delete(0, schemeDataBuff.length());

            } else {
              // ex. schemeName()
              // Should we throw an exception with a more suitable message instead??
              return false;
            }
        }
      } // end while
      return true;
    }

    /**
     * Scans a NCName.
     * From Namespaces in XML
     * [5] NCName ::= (Letter | '_') (NCNameChar)*
     * [6] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
     *
     * @param data A String containing the XPointer expression
     * @param endOffset The int XPointer expression length
     * @param currentOffset An int representing the current position of the XPointer expression
     * pointer
     */
    private int scanNCName(String data, int endOffset, int currentOffset) {
      int ch = data.charAt(currentOffset);
      if (ch >= 0x80) {
        if (!XMLChar.isNameStart(ch)) {
          return currentOffset;
        }
      } else {
        byte chartype = fASCIICharMap[ch];
        if (chartype != CHARTYPE_LETTER
            && chartype != CHARTYPE_UNDERSCORE) {
          return currentOffset;
        }
      }

      //while (currentOffset++ < endOffset) {
      while (++currentOffset < endOffset) {
        ch = data.charAt(currentOffset);
        if (ch >= 0x80) {
          if (!XMLChar.isName(ch)) {
            break;
          }
        } else {
          byte chartype = fASCIICharMap[ch];
          if (chartype != CHARTYPE_LETTER
              && chartype != CHARTYPE_DIGIT
              && chartype != CHARTYPE_PERIOD
              && chartype != CHARTYPE_MINUS
              && chartype != CHARTYPE_UNDERSCORE) {
            break;
          }
        }
      }
      return currentOffset;
    }

    /**
     * Scans the SchemeData.
     * [6]    SchemeData   ::=    EscapedData*
     * [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
     * [8]    NormalChar   ::=    UnicodeChar - [()^]
     * [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
     */
    private int scanData(String data, StringBuffer schemeData,
        int endOffset, int currentOffset) {
      while (true) {

        if (currentOffset == endOffset) {
          break;
        }

        int ch = data.charAt(currentOffset);
        byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
            : fASCIICharMap[ch];

        if (chartype == CHARTYPE_OPEN_PAREN) {
          schemeData.append(ch);
          //schemeData.append(Tokens.XPTRTOKEN_OPEN_PAREN);
          currentOffset = scanData(data, schemeData, endOffset,
              ++currentOffset);
          if (currentOffset == endOffset) {
            return currentOffset;
          }

          ch = data.charAt(currentOffset);
          chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
              : fASCIICharMap[ch];

          if (chartype != CHARTYPE_CLOSE_PAREN) {
            return endOffset;
          }
          schemeData.append((char) ch);
          ++currentOffset;//

        } else if (chartype == CHARTYPE_CLOSE_PAREN) {
          return currentOffset;

        } else if (chartype == CHARTYPE_CARRET) {
          ch = data.charAt(++currentOffset);
          chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
              : fASCIICharMap[ch];

          if (chartype != CHARTYPE_CARRET
              && chartype != CHARTYPE_OPEN_PAREN
              && chartype != CHARTYPE_CLOSE_PAREN) {
            break;
          }
          schemeData.append((char) ch);
          ++currentOffset;

        } else {
          schemeData.append((char) ch);
          ++currentOffset;//
        }
      }

      return currentOffset;
    }

    //
    // Protected methods
    //

    /**
     * This method adds the specified token to the token list. By
     * default, this method allows all tokens. However, subclasses
     * of the XPathExprScanner can override this method in order
     * to disallow certain tokens from being used in the scanned
     * XPath expression. This is a convenient way of allowing only
     * a subset of XPath.
     */
    protected void addToken(Tokens tokens, int token) throws XNIException {
      tokens.addToken(token);
    } // addToken(int)

  } // class Scanner

  // ************************************************************************
  //  Overridden XMLDocumentHandler methods
  // ************************************************************************

  /**
   * If the comment is a child of a matched element, then pass else return.
   *
   * @param text The text in the comment.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by application to signal an error.
   */
  public void comment(XMLString text, Augmentations augs) throws XNIException {
    if (!isChildFragmentResolved()) {
      return;
    }
    super.comment(text, augs);
  }

  /**
   * A processing instruction. Processing instructions consist of a
   * target name and, optionally, text data. The data is only meaningful
   * to the application.
   * <p>
   * Typically, a processing instruction's data will contain a series
   * of pseudo-attributes. These pseudo-attributes follow the form of
   * element attributes but are <strong>not</strong> parsed or presented
   * to the application as anything other than text. The application is
   * responsible for parsing the data.
   *
   * @param target The target.
   * @param data The data or null if none specified.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void processingInstruction(String target, XMLString data,
      Augmentations augs) throws XNIException {
    if (!isChildFragmentResolved()) {
      return;
    }
    super.processingInstruction(target, data, augs);
  }

  /**
   * The start of an element.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startElement(QName element, XMLAttributes attributes,
      Augmentations augs) throws XNIException {
    if (!resolveXPointer(element, attributes, augs,
        XPointerPart.EVENT_ELEMENT_START)) {

      // xml:base and xml:lang processing
      if (fFixupBase) {
        processXMLBaseAttributes(attributes);
      }
      if (fFixupLang) {
        processXMLLangAttributes(attributes);
      }

      // set the context invalid if the element till an element from the result infoset is included
      fNamespaceContext.setContextInvalid();

      return;
    }
    super.startElement(element, attributes, augs);
  }

  /**
   * An empty element.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void emptyElement(QName element, XMLAttributes attributes,
      Augmentations augs) throws XNIException {
    if (!resolveXPointer(element, attributes, augs,
        XPointerPart.EVENT_ELEMENT_EMPTY)) {
      // xml:base and xml:lang processing
      if (fFixupBase) {
        processXMLBaseAttributes(attributes);
      }
      if (fFixupLang) {
        processXMLLangAttributes(attributes);
      }
      // no need to restore restoreBaseURI() for xml:base and xml:lang processing

      // set the context invalid if the element till an element from the result infoset is included
      fNamespaceContext.setContextInvalid();
      return;
    }
    super.emptyElement(element, attributes, augs);
  }

  /**
   * Character content.
   *
   * @param text The content.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void characters(XMLString text, Augmentations augs)
      throws XNIException {
    if (!isChildFragmentResolved()) {
      return;
    }
    super.characters(text, augs);
  }

  /**
   * Ignorable whitespace. For this method to be called, the document
   * source must have some way of determining that the text containing
   * only whitespace characters should be considered ignorable. For
   * example, the validator can determine if a length of whitespace
   * characters in the document are ignorable based on the element
   * content model.
   *
   * @param text The ignorable whitespace.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void ignorableWhitespace(XMLString text, Augmentations augs)
      throws XNIException {
    if (!isChildFragmentResolved()) {
      return;
    }
    super.ignorableWhitespace(text, augs);
  }

  /**
   * The end of an element.
   *
   * @param element The name of the element.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endElement(QName element, Augmentations augs)
      throws XNIException {
    if (!resolveXPointer(element, null, augs,
        XPointerPart.EVENT_ELEMENT_END)) {

      // no need to restore restoreBaseURI() for xml:base and xml:lang processing
      return;
    }
    super.endElement(element, augs);
  }

  /**
   * The start of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startCDATA(Augmentations augs) throws XNIException {
    if (!isChildFragmentResolved()) {
      return;
    }
    super.startCDATA(augs);
  }

  /**
   * The end of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endCDATA(Augmentations augs) throws XNIException {
    if (!isChildFragmentResolved()) {
      return;
    }
    super.endCDATA(augs);
  }

  // ************************************************************************
  // Overridden XMLComponent methods
  // ************************************************************************

  /**
   * <p>
   * Sets the value of a property. This method is called by the component
   * manager any time after reset when a property changes value.
   * </p>
   * <strong>Note:</strong> Components should silently ignore properties
   * that do not affect the operation of the component.
   *
   * @param propertyId The property identifier.
   * @param value The value of the property.
   * @throws XMLConfigurationException Thrown for configuration error. In general, components should
   * only throw this exception if it is <strong>really</strong> a critical error.
   */
  public void setProperty(String propertyId, Object value)
      throws XMLConfigurationException {

    // Error reporter
    if (propertyId == Constants.XERCES_PROPERTY_PREFIX
        + Constants.ERROR_REPORTER_PROPERTY) {
      if (value != null) {
        fXPointerErrorReporter = (XMLErrorReporter) value;
      } else {
        fXPointerErrorReporter = null;
      }
    }

    // Error handler
    if (propertyId == Constants.XERCES_PROPERTY_PREFIX
        + Constants.ERROR_HANDLER_PROPERTY) {
      if (value != null) {
        fErrorHandler = (XMLErrorHandler) value;
      } else {
        fErrorHandler = null;
      }
    }

    // xml:lang
    if (propertyId == Constants.XERCES_FEATURE_PREFIX
        + Constants.XINCLUDE_FIXUP_LANGUAGE_FEATURE) {
      if (value != null) {
        fFixupLang = ((Boolean) value).booleanValue();
      } else {
        fFixupLang = false;
      }
    }

    // xml:base
    if (propertyId == Constants.XERCES_FEATURE_PREFIX
        + Constants.XINCLUDE_FIXUP_BASE_URIS_FEATURE) {
      if (value != null) {
        fFixupBase = ((Boolean) value).booleanValue();
      } else {
        fFixupBase = false;
      }
    }

    //
    if (propertyId == Constants.XERCES_PROPERTY_PREFIX
        + Constants.NAMESPACE_CONTEXT_PROPERTY) {
      fNamespaceContext = (XIncludeNamespaceSupport) value;
    }

    super.setProperty(propertyId, value);
  }

}
